""" 
Copyright 2009 Texas A&M University
 
Licensed under the Apache License, Version 2.0 (the "License"); you may not use 
this file except in compliance with the License. You may obtain a copy of the 
License at

http://www.apache.org/licenses/LICENSE-2.0
  
Unless required by applicable law or agreed to in writing, software distributed 
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 
CONDITIONS OF ANY KIND, either express or implied. See the License for the 
specific language governing permissions and limitations under the License.
"""
from logging import getLogger

import zope.interface
from zope.component import provideUtility

from pygraph import digraph
from pygraph.algorithms.minmax import shortest_path

from meercat.mapreduce.interfaces import IMapper, IMapperRegistry, IChainedMapper
from meercat.mapreduce.exceptions import *
from meercat.mapreduce.mappers import ChainedMapper



log = getLogger('meercat.mapreduce.registry')

class IdentityMapper(object):
    zope.interface.implements(IMapper)
    
    def __init__(self, format):
        self._format = format
    
    def getFormats(self):
        return [(format, format)]
    
    def getOutputFormat(self, input_format):
        return input_format
    
    def map(self, metadata):
        return metadata

class GraphMapperRegistry(object):
    zope.interface.implements(IMapperRegistry)
    
    def __init__(self, chain_class = ChainedMapper):
        self._graph = digraph()
        self._cache = {}
        
        if not IChainedMapper.implementedBy(chain_class):
            raise MapError(u'The chain_class must implement the IChainedMapper '
                           'interface')
        
        self._chain_class = chain_class
    
    def _clearCache(self):
        self._cache = {}
    
    def register(self, mapper):
        """ Register a mapper. """
        
        if not IMapper.providedBy(mapper):
            raise RegistrationError(u'The provided mapper does not provide the '
                                    'IMapper interface')
        
        self._clearCache()
        
        try:
            for edge in mapper.getFormats():
                try:
                    self._graph.add_node(edge[0])
                    self._graph.add_node(edge[1])
                    self._graph.add_edge(edge[0],edge[1])
                    self._graph.set_edge_label(edge[0],edge[1],mapper)
                except Exception, e:
                    raise MapperRegistrationError(u'Error adding the mapper '
                                                  '%s: %s' % (str(edge),
                                                              str(e),))
        except Exception, e:
            raise MapperRegistrationError(u'Error extracting formats from the '
                                          'mapper')
    
    def unregister(self, mapper):
        log.debug('%s.unregister()' % (str(self),))
        self._clearCache()
        map(lambda e: self._graph.del_edge(*e),
            filter(lambda e: self._graph.edge_label(*e) == mapper,
                   self._graph.edges()))
    
    def getMapper(self, source_format, destination_format):
        """ Find a mapper that goes from the source format to the
        destination format """
        
        t = (source_format, destination_format,)
        
        if t in self._cache:
            return self._cache[t]
        
        if source_format == destination_format:
            self._cache[t] = IdentityMapper(format=source_format)
            return self._cache[t]
        
        if not self._graph.has_node(source_format):
            raise UnkownFormat(source_format)
        
        if not self._graph.has_node(destination_format):
            raise UnkownFormat(destination_format)
        
        tree, distance = shortest_path(self._graph, source_format)
        
        if not destination_format in distance:
            raise NoPath(source_format, destination_format)
        
        destination = destination_format
        source = tree[destination]
        
        chain = []
        
        while source is not None:        
            chain.append(self._graph.edge_label(source,destination))
            destination = source
            source = tree[destination]
        
        if len(chain) > 1:
            chain.reverse()
            mapper = self._chain_class(source_format)
        
            for map in chain:
                mapper.append(map)
        elif len(chain) == 1:
            mapper = chain[0]
        
        self._cache[t] = mapper
        
        return mapper
    
    def listMappers(self):
        return frozenset(map(lambda edge: self._graph.edge_label(*edge),
                             self._graph.edges()))

GlobalMapperRegistry = GraphMapperRegistry()
provideUtility(component=GlobalMapperRegistry,
               provides=IMapperRegistry)

def register_mapper(mapper, registry=GlobalMapperRegistry):
    """Register a transformer with a registry. This is a shortcut for 
    registering with the global transformer registry """
    registry.register(mapper)